#include "httpmsg.hxx"

#include <curl/curl.h>
#include <util.hxx>

#include <cassert>

#pragma comment(lib, "libcurl")

using Util::IsReadyForWrite;
using Util::IsReadyForRead;
using Util::CurlAsErrorId;

using namespace std;

bool HttpMsg::Is(const std::string& Type) const
{
	return std::string::npos != header["Content-Type"].find(Type.c_str());
}
std::string HttpMsg::HeaderAsStr(void) const
{
	std::stringstream headerstr;

	if (!method.empty()) headerstr<<method<<"\r\n";

	std::map<std::string, std::string>::const_iterator iter = header.begin();
	while (iter != header.end())
	{
		headerstr<<iter->first<<": "<<iter->second<<"\r\n";

		++iter;
	}

	/* 2 CRLF line as separation between Header and Body. */
	headerstr<<"\r\n";

	return headerstr.str();
}

/**
* Return : 0 succeeded, 1 not ready, 2 sending not complete
*/
int HttpMsg::Send(CURL* curl, int vl, const char* curlError)
{
	if (IsReadyForWrite(curl, vl) <= 0)
		return 1;

	CURLcode res;
	size_t n;

	string header = HeaderAsStr();	
	res = curl_easy_send(curl, (void*)(header.c_str()), header.length(), &n);

	if (res != CURLE_OK)
		return res;

	if(n != header.length())
		return 2;

	int errorid = CurlAsErrorId(curlError);
	if (CURLE_OK != errorid)
		return errorid;

	if (body.gcount())
	{
		string bodyStr = body.str();
		res = curl_easy_send(curl, (void*)(bodyStr.c_str()), bodyStr.length(), &n);

		if (res != CURLE_OK)
			return res;

		if(n != bodyStr.length())
			return 2;

		int errorid = CurlAsErrorId(curlError);
		if (CURLE_OK != errorid)
			return errorid;
	}

	assert(strlen(curlError) == 0);

	return 0;
}

int HttpMsg::RecvHttpStream(CURL* curl, int vl, int contentLen)
{
	const int BufLen = 2048;
	char buf[BufLen];

	size_t n;
	CURLcode res;

	int contentReaded = 0;

	while (((contentLen == 0 && contentReaded == 0) || contentReaded < contentLen) && IsReadyForRead(curl, --vl) && vl >= 0)
	{
		res = curl_easy_recv(curl, (void*)buf, (((contentLen - contentReaded) > BufLen) || !contentLen) ? BufLen : (contentLen - contentReaded), &n);

		body.write(buf, static_cast<streamsize>(n));

		contentReaded += static_cast<int>(n);
	}

	return contentReaded;
}
int HttpMsg::Recv(CURL* curl, int vl)
{
	const string CR = "\r";
	const string CR2 = "\r\r";
	const string CR3 = "\r\n";

	bool headerStarted = false;
	bool headerCompleted = false;
	bool completed = false;
	bool failed = false;

	string line;
	while (!headerCompleted && !failed)
	{
		getline(body, line);

		if (body.eof())
		{
			body.clear();
			if (0 == RecvHttpStream(curl, vl))
			{
				failed = true;
				continue;
			}
		}

		/************************************************************************************
		* thought cannot find by HTTP/1. for there might be this : HTTP/1.1 100 Continue.	*
		* but 100 Continue is also regarded as a complete message, so we can.				*
		*************************************************************************************/
		if (!headerStarted && string::npos != line.find("HTTP/1."))
		{
			method = line;
			headerStarted = true;
		}

		if (headerStarted)
		{
			if (!headerCompleted)
			{
				if (line == CR || line == CR2 || line == CR3)
				{
					headerCompleted = true;
				}
				else if (!line.empty())
				{
					/* not using split as "Date: Thu, 03 Jan 2013 10:04:02 GMT" in Header. */
					string key = line.substr(0, line.find(": "));
					string vl = line.substr(line.find(": ") + 2);
					header[key] = vl;
				}	
			}
		}
	}

	if (!headerCompleted)
		return 0;

	int contentLen = atoi(header["Content-Length"].c_str());
	if (contentLen == 0)
		return 1;

	int contentReaded = 0;
	char* bodyBuf = new char[contentLen];
	while (body.gcount() < contentLen && !failed)
	{
		if (body.eof())
			body.clear();

		if (!RecvHttpStream(curl, vl, (contentLen-contentReaded)))
		{
			failed = true;

			contentReaded = 0; // unique errorno.

			continue;
		}
	}

	assert(contentReaded == contentLen);

	responseLen = contentReaded;

	return contentReaded;
}
